// ==UserScript==
// @name         LLM Stream Demo
// @namespace    www.roginx.ink
// @version      1.0
// @description  使用fetch向SiliconFlow进行流式请求，并解析tool_calls执行JS
// @match        https://www.educoder.net/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';
    class Tools {
        constructor() {
            this.tools = [];
            this.currCodeId = 0;
            this.currCode = '';

            this.register(
                'NULLTools',
                '防止出现工具错误的占位工具，当agent发现没有可用工具时调用它，但实际不执行任何操作。',
                [],
                {}
            );

            // 注册 run_js_code
            this.register(
                'run_js_code',
                '在浏览器执行一段JavaScript代码',
                ['code'],
                {
                    code: { type: 'string', description: '要执行的JavaScript代码' }
                }
            );
        }

        register(name, description, required_parameters, parameters) {
            if (this.tools.some(tool => tool.name === name)) {
                throw new Error(`工具 "${name}" 已存在`);
            }
            if (!Array.isArray(required_parameters)) {
                throw new Error('required_parameters 必须是数组');
            }
            if (typeof parameters !== 'object' || parameters === null) {
                throw new Error('parameters 必须是对象');
            }

            this.tools.push({
                name,
                description,
                required_parameters,
                parameters
            });
        }

        findTool(name) {
            return this.tools.find(tool => tool.name === name);
        }
    }

    function formatTools(toolsArray) {
        return toolsArray.map(tool => {
            const functionParams = {
                type: 'object',
                required: tool.required_parameters,
                properties: {}
            };
            Object.entries(tool.parameters).forEach(([paramName, paramDetails]) => {
                functionParams.properties[paramName] = paramDetails;
            });

            return {
                type: 'function',
                function: {
                    name: tool.name,
                    description: tool.description,
                    parameters: functionParams
                },
                path: '' // 如果需要可加额外信息
            };
        });
    }

    // 提取完整的JSON字符串
    function extractCompleteJSON(text) {
        const firstBrace = text.indexOf('{');
        if (firstBrace === -1) return null;

        let braceCount = 0;
        let jsonStr = '';
        let started = false;

        for (let i = 0; i < text.length; i++) {
            const char = text[i];

            if (char === '{' && !started) {
                started = true;
                braceCount = 1;
                jsonStr += char;
            } else if (started) {
                jsonStr += char;

                if (char === '{') {
                    braceCount++;
                } else if (char === '}') {
                    braceCount--;

                    if (braceCount === 0) {
                        // 找到了匹配的闭合括号
                        return jsonStr;
                    }
                }
            }
        }

        // 如果没有找到完整的JSON
        return null;
    }

    const toolsManager = new Tools();
    const currQuestion = { question: '', answer: '', code: [] }
    const toolsPayload = formatTools(toolsManager.tools);
    const scriptHandle = { questionResolving : false }
    let questionHandler;
    let appConfig = null;
    
    // 从后端获取应用配置
    async function fetchAppConfig(name) {
        try {
            console.log(`开始获取应用配置: ${name}`);
            const response = await fetch(`http://localhost:8085/app-config/search/name?name=${encodeURIComponent(name)}`, {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            });
            
            if (!response.ok) {
                throw new Error(`获取应用配置失败: ${response.status} ${response.statusText}`);
            }
            
            const responseData = await response.json();
            console.log('获取应用配置原始响应:', responseData);
            
            // 处理嵌套数据结构，提取data字段
            const config = responseData.data || responseData;
            console.log('解析后的应用配置:', config);
            
            return config;
        } catch (error) {
            console.error('获取应用配置错误:', error);
            return null;
        }
    }
    
    // 从后端获取AI模型配置
    async function fetchAIModelConfig() {
        try {
            console.log('开始获取AI模型配置');
            const response = await fetch('http://localhost:8085/ai-model-config/default', {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            });
            
            if (!response.ok) {
                throw new Error(`获取AI模型配置失败: ${response.status} ${response.statusText}`);
            }
            
            const responseData = await response.json();
            console.log('获取AI模型配置原始响应:', responseData);
            
            // 处理嵌套数据结构，提取data字段
            const config = responseData.data || responseData;
            console.log('解析后的AI模型配置:', config);
            
            return config;
        } catch (error) {
            console.error('获取AI模型配置错误:', error);
            return null;
        }
    }
    
    async function fetchLLMStream(userText) {
        // 如果没有配置，使用默认值
        if (!appConfig) {
            appConfig = await fetchAppConfig('头歌考试');
        }
        
        // 获取AI模型配置
        const modelConfig = await fetchAIModelConfig();
        
        if (!modelConfig) {
            console.error('无法获取AI模型配置，使用默认值');
        }
        
        // 从配置中获取API信息，避免硬编码
        const url = modelConfig?.baseUrl || 'https://api.siliconflow.cn/v1/chat/completions';
        const apiKey = modelConfig?.apiKey || '';
        
        if (!apiKey) {
            console.error('未找到API密钥，请确保已配置AI模型');
            return;
        }
        
        // 组装大模型请求体
        const payload = {
            model: appConfig?.extraConfig?.model || 'Qwen/Qwen2.5-7B-Instruct',
            messages: [
                ...(appConfig?.messages || [
                    { role: 'system', content: '#你所有的回答都是JSON格式【你所有的回答都是JSON格式】' },
                    { role: 'system', content: '不要多余解释！单选题多选题和判断题只要答案的选项即可（单选题answer字段是只有一个元素的数组，多选题answer字段是数组，判断题数组元素是正确或者是错误）,最终输出格式为:{ "answer": ["A","*"]  }' },
                    { role: 'system', content: '不要多余解释！填空题一个问题可能有多个空要确保，一个填空标志为▁▁▁▁▁，或者识别填空项1,填空项2,填空项3...作为最终数组的length，不要多出数组！答案是数组{ "answer": ["填空答案一","二"]  }' }
                ]),
                { role: 'user', content: userText },
            ],
            stream: true,
            temperature: appConfig?.extraConfig?.temperature || 0.3,
            top_p: appConfig?.extraConfig?.top_p || 0.9,
            // tools: toolsPayload
        };

        // 发起请求
        console.log('开始请求 API...', url);
        console.log('请求参数:', payload);
        
        try {
            const response = await fetch(url, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${apiKey}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(payload)
            });

            if (!response.ok) {
                const errorText = await response.text();
                console.error(`API请求失败: ${response.status} ${response.statusText}`, errorText);
                return;
            }

            if (!response.body) {
                console.error('No response body from API');
                return;
            }

            // 使用 fetch 的原生流式读取
            const reader = response.body.getReader();
            const decoder = new TextDecoder('utf-8');
            let buffer = '';

            while (true) {
                const { value, done } = await reader.read();
                if (done) {
                    console.log('API 流结束');
                    break;
                }
                const chunk = decoder.decode(value, { stream: true });
                buffer += chunk;

                const lines = buffer.split('\n');
                buffer = lines.pop() || '';

                for (let line of lines) {
                    line = line.trim();
                    if (!line.startsWith('data: ')) continue;

                    const jsonStr = line.slice(6).trim();
                    if (!jsonStr || jsonStr === '[DONE]') {
                        console.log('API data: [DONE]');
                        continue;
                    }

                    let parsed;
                    try {
                        parsed = JSON.parse(jsonStr);
                    } catch (err) {
                        console.error('JSON parse error:', jsonStr, err);
                        continue;
                    }

                    if (parsed.choices && parsed.choices.length > 0) {
                        for (const choice of parsed.choices) {
                            const delta = choice.delta || {};
                            // 普通文本内容
                            if (delta.content) {
                                // 这里你可以把内容插入到页面，也可以console查看
                                currQuestion.answer += delta.content;
                                console.log('[API Content Chunk]:', delta.content);
                            }

                            // 处理 tool_calls
                            if (delta.tool_calls) {
                                // 说明LLM要调用工具
                                handleToolCalls(delta.tool_calls);
                            }
                        }
                    }
                }
            }

            console.log('API 流读取完毕');
        } catch (error) {
            console.error('API请求错误:', error);
        }
    }

    /***********************
     * 4) 处理工具调用
     ************************/
    function handleToolCalls(toolCalls) {
        // toolCalls 可能是数组
        for (const call of toolCalls) {
            console.log('[API] tool_calls:', call)
            if (call.function.name === 'run_js_code' && call.id.length > 10) {
                toolsManager.currCodeId = call.id;
                toolsManager.currCode = '';
             } else {
                toolsManager.currCode += call.function.arguments;

                // 当 currCode 以 '}' 结尾并可JSON.parse，则说明完整
                if (toolsManager.currCode.endsWith('}')) {
                    try {
                        const argObj = JSON.parse(toolsManager.currCode);
                        console.log('%c[API] 即将执行JS:', 'color: blue;', argObj.code);
                        eval(argObj.code);
                    } catch (e) {
                        console.error('JSON parse or eval error:', e);
                    }
                    // 重置
                    toolsManager.currCode = '';
                }
            }
        }
    }

    class QuestionHandler {
        constructor(outputDiv, fetchLLMStream, findElementBySimilarText) {
            this.outputDiv  = outputDiv;
            this.fetchLLMStream  = fetchLLMStream;
            this.findElementBySimilarText  = findElementBySimilarText;
            this.code  = [];
            this.currQuestion  = { answer: "" };
            this.reTimeout  = null;
        }
        async finishOneQuestion() {
            try {
                // 使用配置中的选择器获取题目类型
                const selector = appConfig?.selector || '[class^="questionTypeTitle___"]';
                let questionType = document.querySelector(selector)?.textContent?.trim().slice(2);
                let questionElement = document.querySelector('[class^="questionPart___"]');
                
                if (!questionElement) {
                    console.warn('  找不到题目元素');
                    return;
                }
                
                let text = questionElement.textContent;
                this.outputDiv.innerHTML  += "<br />题目：" + text;
                
                switch (questionType) {
                    case '填空题':
                        await this.handleInput(text);
                    case '简答题':
                        this.handleFillOrShortAnswer();
                        break;
                    case '单选题':
                    case '多选题':
                        await this.handleSingleOrMultipleChoice(text);
                        break;
                    case '判断题':
                        await this.handleTrueOrFalse(text);
                        break;
                    case '编程题':
                        this.handleProgramming();
                        break;
                    default:
                        console.warn('  未知题型:', questionType);
                }
            } catch (error) {
                console.error('  处理题目时出错:', error);
            }
        }

        // 处理填空题或简答题
        async handleInput(text) {
            await this.fetchLLMStream(text);
            let inputs = document.querySelectorAll('[class*="fillInput___"]');
            this.outputDiv.innerHTML  += `<br />答案：${currQuestion.answer}`;

            // 使用提取函数获取完整JSON
            const jsonString = extractCompleteJSON(currQuestion.answer);
            let ansList = [];

            if (jsonString) {
                try {
                    ansList = JSON.parse(jsonString).answer;
                } catch (e) {
                    console.error('解析JSON失败:', e);
                }
            } else {
                console.warn('未能提取到完整JSON');
            }

            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
                window.HTMLInputElement.prototype,
                "value"
            ).set;
            for (let i = 0; i < ansList.length;  i++) {
                nativeInputValueSetter.call(inputs[i],  ansList[i]);
                await new Promise(resolve => setTimeout(resolve, 1000));
                inputs[i].dispatchEvent(new  Event('input', { bubbles: true }));
                inputs[i].dispatchEvent(new Event('focusout', { bubbles: true }));
                await new Promise(resolve => setTimeout(resolve, 3000));
            }
            this.nextQuestion();
        }
        nextQuestion() {
            const nextBtn = [...document.querySelectorAll('span')].find(
                span => span.textContent.trim()  === "下一题"
            );
            if (nextBtn) {
                nextBtn.click();
            } else {
                console.warn('  未找到"下一题"按钮');
            }

            currQuestion.answer  = "";
            this.reTimeout  = setTimeout(() => this.finishOneQuestion(),  2000);
        }
        // 处理单选题或多选题
        async handleSingleOrMultipleChoice(text) {
            await this.fetchLLMStream(text);
            this.outputDiv.innerHTML  += `<br />答案：${currQuestion.answer}`;

            // 使用提取函数获取完整JSON
            const jsonString = extractCompleteJSON(currQuestion.answer);
            let ansList = [];

            if (jsonString) {
                try {
                    ansList = JSON.parse(jsonString).answer;
                } catch (e) {
                    console.error('解析JSON失败:', e);
                }
            } else {
                console.warn('未能提取到完整JSON');
            }

            for (let i = 0; i < ansList.length;  i++) {
                let elements = document.querySelectorAll('[class^="answerWrap___"]   label:not([class*="ant-checkbox-wrapper-checked"]) .font16');
                let match = this.findElementBySimilarText(ansList[i],  0.1, elements);

                if (match) {
                    this.outputDiv.innerHTML  += `<br />`;
                    match.click();
                    await new Promise(resolve => setTimeout(resolve, 500));
                } else {
                    console.warn('  未找到匹配的选项');
                }
            }
            this.nextQuestion();
        }

        // 处理判断题
        async handleTrueOrFalse(text) {
            await this.fetchLLMStream(text);
            this.outputDiv.innerHTML  += `<br />答案：${currQuestion.answer}`;

            // 使用提取函数获取完整JSON
            const jsonString = extractCompleteJSON(currQuestion.answer);
            let ansList = [];

            if (jsonString) {
                try {
                    ansList = JSON.parse(jsonString).answer;
                } catch (e) {
                    console.error('解析JSON失败:', e);
                }
            } else {
                console.warn('未能提取到完整JSON');
            }

            for (let i = 0; i < ansList.length;  i++) {
                let elements = document.querySelectorAll('[class^="answerWrap___"] label');
                let match = this.findElementBySimilarText(ansList[i],  0.1, elements);

                if (match) {
                    this.outputDiv.innerHTML  += `<br />`;
                    match.click();
                    await new Promise(resolve => setTimeout(resolve, 500));
                } else {
                    console.warn('  未找到匹配的选项');
                }
            }
            this.nextQuestion();
        }
        // 处理编程题
        handleProgramming() {
            // 这里可以添加编程题的处理逻辑
            console.log(' 处理编程题');
        }

        // 开始处理题目
        start() {
            this.reTimeout  = setTimeout(() => this.finishOneQuestion(),  2000);
        }
        stop() {
            clearTimeout(this.reTimeout);
            currQuestion.answer  = "";
        }
    }
    /***********************
     * 5) 在页面上添加一个简单按钮，用来触发fetchLLMStream
     ************************/
    function createUI() {
        function findElementBySimilarText(targetText, threshold = 0.8, allElements) {
            let bestMatch = null;
            let bestScore = 0;

            for (const el of allElements) {
                // if (el.children.length  === 0) continue; // 跳过纯文本节点
                const text = el.textContent.trim();
                const score = similarity(text, targetText);
                if (score > bestScore && score >= threshold) {
                    bestScore = score;
                    bestMatch = el;
                }
            }

            return bestMatch;
        }

        // 简单字符串相似度（Jaccard index 或更简单的方法）
        function similarity(s1, s2) {
            if (!s1 || !s2) return 0;
            s1 = s1.toLowerCase();
            s2 = s2.toLowerCase();
            const common = s1.split('').filter(c  => s2.includes(c));
            return common.length  / Math.max(s1.length,  s2.length);
        }
        
        // 创建配置对话框
        function createConfigDialog() {
            const dialogContainer = document.createElement('div');
            dialogContainer.className = 'app-config-dialog';
            dialogContainer.style.display = 'none';
            dialogContainer.style.position = 'fixed';
            dialogContainer.style.top = '50%';
            dialogContainer.style.left = '50%';
            dialogContainer.style.transform = 'translate(-50%, -50%)';
            dialogContainer.style.backgroundColor = 'white';
            dialogContainer.style.padding = '20px';
            dialogContainer.style.borderRadius = '8px';
            dialogContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
            dialogContainer.style.zIndex = '10000';
            dialogContainer.style.width = '400px';
            
            const dialogTitle = document.createElement('h3');
            dialogTitle.textContent = '应用配置';
            dialogTitle.style.marginTop = '0';
            
            const configNameLabel = document.createElement('div');
            configNameLabel.textContent = '配置名称:';
            configNameLabel.style.marginTop = '10px';
            
            const configNameInput = document.createElement('input');
            configNameInput.type = 'text';
            configNameInput.value = '头歌考试';
            configNameInput.style.width = '100%';
            configNameInput.style.padding = '8px';
            configNameInput.style.marginTop = '5px';
            configNameInput.style.boxSizing = 'border-box';
            
            const buttonContainer = document.createElement('div');
            buttonContainer.style.display = 'flex';
            buttonContainer.style.justifyContent = 'flex-end';
            buttonContainer.style.marginTop = '20px';
            buttonContainer.style.gap = '10px';
            
            const cancelButton = document.createElement('button');
            cancelButton.textContent = '取消';
            cancelButton.style.padding = '6px 12px';
            cancelButton.onclick = () => {
                dialogContainer.style.display = 'none';
            };
            
            const confirmButton = document.createElement('button');
            confirmButton.textContent = '确认';
            confirmButton.style.padding = '6px 12px';
            confirmButton.style.backgroundColor = '#165DFF';
            confirmButton.style.color = 'white';
            confirmButton.style.border = 'none';
            confirmButton.style.borderRadius = '4px';
            confirmButton.onclick = async () => {
                const configName = configNameInput.value.trim();
                if (configName) {
                    appConfig = await fetchAppConfig(configName);
                    if (appConfig) {
                        outputDiv.innerHTML += `<br>已加载配置: ${appConfig.name}`;
                    } else {
                        outputDiv.innerHTML += `<br>未找到配置: ${configName}`;
                    }
                }
                dialogContainer.style.display = 'none';
            };
            
            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(confirmButton);
            
            dialogContainer.appendChild(dialogTitle);
            dialogContainer.appendChild(configNameLabel);
            dialogContainer.appendChild(configNameInput);
            dialogContainer.appendChild(buttonContainer);
            
            document.body.appendChild(dialogContainer);
            
            return dialogContainer;
        }
        
        // 添加一个输出内容div
        const outputDiv = document.createElement('div');
        outputDiv.style.position  = 'fixed';
        outputDiv.style.top  = '100px';
        outputDiv.style.right  = '20px';
        outputDiv.style.zIndex  = 99999;
        outputDiv.style.backgroundColor  = 'rgba(0, 0, 0, 0.8)';
        outputDiv.style.padding  = '10px';
        outputDiv.style.color  = 'white';
        outputDiv.style.borderRadius  = '5px';
        outputDiv.style.maxWidth  = '300px';
        
        const configDialog = createConfigDialog();
        
        // 创建按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style.position = 'fixed';
        buttonContainer.style.top = '50px';
        buttonContainer.style.right = '20px';
        buttonContainer.style.zIndex = 99999;
        buttonContainer.style.display = 'flex';
        buttonContainer.style.gap = '10px';
        
        // 开始/停止按钮
        const btn = document.createElement('button');
        btn.textContent  = '开启答题';
        btn.style.padding = '8px 16px';
        btn.style.backgroundColor = '#165DFF';
        btn.style.color = 'white';
        btn.style.border = 'none';
        btn.style.borderRadius = '4px';
        
        // 配置按钮
        const configBtn = document.createElement('button');
        configBtn.textContent = '配置';
        configBtn.style.padding = '8px 16px';
        configBtn.style.backgroundColor = '#36cfc9';
        configBtn.style.color = 'white';
        configBtn.style.border = 'none';
        configBtn.style.borderRadius = '4px';
        configBtn.onclick = () => {
            configDialog.style.display = 'block';
        };
        
        questionHandler = new QuestionHandler(outputDiv, fetchLLMStream, findElementBySimilarText);
        
        buttonContainer.appendChild(btn);
        buttonContainer.appendChild(configBtn);
        document.body.appendChild(buttonContainer);
        document.body.appendChild(outputDiv);
        
        btn.onclick  = async () => {
            if (!scriptHandle.questionResolving) {
                scriptHandle.questionResolving = true;
                questionHandler.start();
            } else {
                scriptHandle.questionResolving = false;
                questionHandler.stop();
            }
            btn.textContent = scriptHandle.questionResolving ? '停止答题' : '开启答题';
            // const userPrompt = prompt('请输入要给LLM的指令：', '');
            // if (!userPrompt) return;
            // await fetchLLMStream(userPrompt);
        };

        function simulateUserClick(element) {
            if (!element) return;

            const rect = element.getBoundingClientRect();
            const centerX = rect.left  + rect.width  / 2;
            const centerY = rect.top  + rect.height  / 2;

            ['mousedown', 'mouseup', 'click'].forEach(type => {
                const event = new MouseEvent(type, {
                    bubbles: true,
                    cancelable: true,
                    view: window,
                    clientX: centerX,
                    clientY: centerY,
                });
                element.dispatchEvent(event);
            });
        }
    }
    createUI();

})();